;--------------------定义常量--------------------
;FAT16目录项中各成员的偏移量：
;名称                 偏移   长度    描述
DIR_Name        equ    0     ;11    文件名8B，扩展名3B
DIR_Attr        equ    11    ;1     目录项属性
;Reserved       equ    12    ;10    保留位
DIR_WrtTime     equ    22    ;2     最后一次写入时间
DIR_WrtDate     equ    24    ;2     最后一次写入日期
DIR_FstClus     equ    26    ;2     起始簇号
DIR_FileSize    equ    28    ;4     文件大小

BOOT_ADDRESS equ 0x7c00 ;boot程序加载到内存的地址。
FILE_ADDRESS equ 0x1000 ;文件读到内存中的地址。
DISK_BUFFER  equ 0x7e00 ;读磁盘临时存放数据用的缓存区，放到boot程序之后。
DISK_SIZE_M equ 4 ;磁盘容量，单位M。
FAT1_SECTORS     equ 32 ;FAT1占用扇区数
ROOT_DIR_SECTORS equ 32 ;根目录占用扇区数
SECTOR_NUM_OF_FAT1_START     equ 1 ;FAT1表起始扇区号
SECTOR_NUM_OF_ROOT_DIR_START equ 33 ;根目录区起始扇区号
SECTOR_NUM_OF_DATA_START     equ 65 ;数据区起始扇区号，对应簇号为2。
SECTOR_CLUSTER_BALANCE       equ 63 ;簇号加上该值正好对应扇区号。
FILE_NAME_LENGTH     equ 11 ;文件名8字节加扩展名3字节共11字节。
DIR_ENTRY_SIZE       equ 32 ;目录项为32字节。
DIR_ENTRY_PER_SECTOR equ 16 ;每个扇区能存放目录项的数目。

;--------------------MBR开始--------------------
org BOOT_ADDRESS
jmp boot_start
nop

;FAT16参数区：
BS_OEMName 		db 'GrapeOS '   ;厂商名称（8字节，含空格）
BPB_BytesPerSec dw 0x0200		;每扇区字节数
BPB_SecPerClus	db 0x01			;每簇扇区数
BPB_RsvdSecCnt	dw 0x0001		;保留扇区数（引导扇区的扇区数）
BPB_NumFATs		db 0x01			;FAT表的份数
BPB_RootEntCnt	dw 0x0200		;根目录可容纳的目录项数
BPB_TotSec16	dw 0x2000 		;扇区总数(4MB)
BPB_Media		db 0xf8 		;介质描述符
BPB_FATSz16		dw 0x0020		;每个FAT表扇区数
BPB_SecPerTrk	dw 0x0020		;每磁道扇区数
BPB_NumHeads	dw 0x0040		;磁头数
BPB_hiddSec		dd 0x00000000	;隐藏扇区数
BPB_TotSec32	dd 0x00000000	;如果BPB_TotSec16是0，由这个值记录扇区数。
BS_DrvNum		db 0x80			;int 13h的驱动器号
BS_Reserved1	db 0x00			;未使用
BS_BootSig		db 0x29			;扩展引导标记
BS_VolID		dd 0x00000000   ;卷序列号
BS_VolLab		db 'Grape OS   ';卷标（11字节，含空格）
BS_FileSysType	db 'FAT16   '	;文件系统类型（8字节，含空格）

;通过以上参数可知硬盘容量为4MB，共8K个扇区。扇区具体分布如下：
;区域名     扇区数      扇区号          字节偏移            说明
;引导扇区   1个扇区     扇区0           0x0000~0x01ff
;FAT1表     32个扇区    扇区1~32        0x0200~0x41ff   可记录8K-2个簇
;FAT2表     无          无              无              无
;根目录区   32个扇区    扇区33~64       0x4200~0x81ff   可容纳512个目录项
;数据区     8127个扇区  扇区65~0x1fff   0x8200~0x3fffff

;--------------------程序开始--------------------
boot_start:
;初始化寄存器
mov ax,cs
mov ds,ax
mov es,ax ;cmpsb会用到ds:si和es:di

;读取文件开始
;读取根目录的第1个扇区（1个扇区可以存放16个目录项，我们用到的文件少，不会超过16个。）
mov esi,SECTOR_NUM_OF_ROOT_DIR_START 
mov di,DISK_BUFFER
call func_read_one_sector

;在16个目录项中通过文件名查找文件
cld ;cld将标志位DF置0，在串处理指令中控制每次操作后让si和di自动递增。std相反。下面repe cmpsb会用到。
mov bx,0 ;用bx记录遍历第几个目录项。
next_dir_entry:
mov si,bx
shl si,5 ;乘以32（目录项的大小）
add si,DISK_BUFFER              ;源地址指向目录项中的文件名。
mov di,read_file_name_string  ;目标地址指向文件在硬盘中的正确文件名。
mov cx,FILE_NAME_LENGTH ;字符比较次数为FAT16文件名长度，每比较一个字符，cx会自动减一。
repe cmpsb ;逐字节比较ds:si和es:di指向的两个字符串。
jcxz file_found ;当cx为0时跳转，cx为0表示上面比较的两个字符串相同。找到了文件。
inc bx
cmp bx,DIR_ENTRY_PER_SECTOR
jl next_dir_entry ;检查下一个目录项。
jmp file_not_found ;没有找到文件。

file_found: ;找到了文件
;从目录项中获取文件的起始簇号
shl bx,5 ;乘以32
add bx,DISK_BUFFER
mov bx,[bx+DIR_FstClus] ;文件的起始簇号

;读取FAT1表的第1个扇区（我们用到的文件少且小，只用到了该扇区中的簇号。）
mov esi,SECTOR_NUM_OF_FAT1_START 
mov di,DISK_BUFFER ;放到boot程序之后
call func_read_one_sector

mov bp,FILE_ADDRESS ;文件内容读取到内存中的起始地址

;按簇号读文件内容
read_file: 
xor esi,esi ;esi清零
mov si,bx ;簇号
add esi,SECTOR_CLUSTER_BALANCE
mov di,bp
call func_read_one_sector
add bp,512 ;下一个目标地址

;获取下一个簇号（每个FAT表项为2字节）
shl bx,1 ;乘2，每个FAT表项占2个字节
mov bx,[bx+DISK_BUFFER]

;判断下一个簇号
cmp bx,0xfff8 ;大于等于0xfff8表示文件的最后一个簇
jb read_file ;jb无符号小于则跳转，jl有符号小于则跳转。

read_file_finish: ;读取文件结束
jmp stop

file_not_found: ;没有找到文件

stop:
hlt
jmp stop

;读取硬盘1个扇区（主硬盘控制器主盘）
;输入参数：esi，ds:di。
;esi LBA扇区号
;ds:di 将数据写入到的内存起始地址
;输出参数：无。
func_read_one_sector:
;第1步：检查硬盘控制器状态
mov dx,0x1f7
.not_ready1:
nop ;nop相当于稍息 hlt相当于睡觉
in al,dx ;读0x1f7端口
and al,0xc0 ;第7位为1表示硬盘忙，第6位为1表示硬盘控制器已准备好，正在等待指令。
cmp al,0x40 ;当第7位为0，且第6位为1，则进入下一个步。
jne .not_ready1 ;若未准备好，则继续判断。
;第2步：设置要读取的扇区数
mov dx,0x1f2
mov al,1
out dx,al ;读取1个扇区
;第3步：将LBA地址存入0x1f3~0x1f6
mov eax,esi
;LBA地址7~0位写入端口0x1f3
mov dx,0x1f3
out dx,al
;LBA地址15~8位写入端口写入0x1f4
shr eax,8
mov dx,0x1f4
out dx,al
;LBA地址23~16位写入端口0x1f5
shr eax,8
mov dx,0x1f5
out dx,al
;第4步：设置device端口
shr eax,8
and al,0x0f ;LBA第24~27位
or al,0xe0 ;设置7~4位为1110，表示LBA模式，主盘
mov dx,0x1f6
out dx,al
;第5步：向0x1f7端口写入读命令0x20
mov dx,0x1f7
mov al,0x20
out dx,al
;第6步：检测硬盘状态
.not_ready2:
nop ;nop相当于稍息 hlt相当于睡觉
in al,dx ;读0x1f7端口
and al,0x88 ;第7位为1表示硬盘忙，第3位为1表示硬盘控制器已准备好数据传输。
cmp al,0x08 ;当第7位为0，且第3位为1，进入下一步。
jne .not_ready2 ;若未准备好，则继续判断。
;第7步：从0x1f0端口读数据
mov cx,256 ;每次读取2字节，一个扇区需要读256次。
mov dx,0x1f0
.go_on_read:
in ax,dx
mov [di],ax
add di,2
loop .go_on_read
ret

read_file_name_string:db "DATA    TXT",0  ;要读取的文件在硬盘中存储的文件名，共11个字节，含空格。

times 510-($-$$) db 0
db 0x55,0xaa